【Rust】 プロセス操作 練習帳
#rust #プロセス
Claude先生に練習帳を作ってもらった。これでプロセス操作に慣れる。
1. 同期的プロセスの起動
hr.icon
code: rust
use std::process::Command;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 単純なコマンドを実行する(例: "echo Hello, World!")
let output = Command::new("echo")
.arg("Hello, World!")
.output()?;
// 出力を表示
println!("ステータスコード: {}", output.status);
println!("標準出力: {}", String::from_utf8_lossy(&output.stdout));
println!("標準エラー: {}", String::from_utf8_lossy(&output.stderr));
Ok(())
}
ポイント
output()メソッドは、同期的にコマンドを実行する。
同期的なので、このコマンドが終わるまではrustコードは停止し続ける。
output()メソッドが返すのは、Outputオブジェクトというもので、ここから標準出力と標準エラー出力、ステータスコードも参照できる。
.status, $output.stdout, $output.stderr
てな感じでね。
stdoutとstderrの型はVec<u8>らしい。文字列として使うにはStringに変換してやる必要がある。
そこで、String::from_utf8_lossyってわけだ。
なお、from_utf8てのもある。こっちの方が厳格で、utf8と読み取れないものがあるとエラー発生させる。
2. 非同期的プロセスの起動
hr.icon
code: rust
use std::process::{Command, Child};
use std::io::Result;
fn main() -> Result<()> {
// プロセスを起動するが、完了を待たない
let mut child = Command::new("sleep")
.arg("3") // 3秒スリープ
.spawn()?;
println!("子プロセスを起動しました (PID: {:?})", child.id());
println!("待機中...");
// プロセスの終了を待つ
let status = child.wait()?;
println!("子プロセスが終了しました: {}", status);
Ok(())
}
ポイント
spawn()関数は非同期でコマンドを実行する。
output()と異なる。
childが、生み出された子プロセスの操作オブジェクト。
これに対していろいろやる。
child.wait()を実行して初めて待つ。
子プロセスが終了したのを検知すると、次に進める。
3. 引数と環境変数を与える
hr.icon
code: rust
use std::process::Command;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 環境変数を設定
let mut cmd = Command::new("env");
// 複数の引数を追加
cmd.args("bash", "-c", "echo $GREETING, $NAME!");
// 環境変数を設定
cmd.env("GREETING", "こんにちは");
cmd.env("NAME", "Rust");
let output = cmd.output()?;
println!("結果: {}", String::from_utf8_lossy(&output.stdout));
Ok(())
}
ポイント
argsで引数を与える。順序大事で複数与えれる。
envで環境変数を設定できる。
4. 標準入力からのデータ供給
hr.icon
code: rust
use std::io::Write;
use std::process::{Command, Stdio};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 標準入力を提供可能なプロセスを作成
let mut child = Command::new("cat") // Windowsの場合は "type" を使用
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
// 子プロセスの標準入力にデータを書き込む
if let Some(stdin) = child.stdin.as_mut() {
stdin.write_all(b"Hello from Rust!\n")?;
// バッファをフラッシュ
let _ = stdin.flush();
}
// 出力を待ち、取得する
let output = child.wait_with_output()?;
println!(
"プロセスからの出力: {}",
String::from_utf8_lossy(&output.stdout)
);
Ok(())
}
ポイント
プロセス間通信のシンプルなデータやり取りは、やはりpipeなんだな。
シンプルでわかりやすいもんな。
生み出したプロセスに対してpipeを繋げる方法
code: rust
.stdin(Stdio::piped())
.stdout(Stdio::piped())
入力もするし出力も受ける。
これがないとpipeによるプロセス間通信ができない。
もしstdinを設定しない場合、Stdio::inherit()が設定されて、子プロセスは親プロセスと同じ入力を受けることになる。
stdinの可変参照を受け取り、そこに文字入力してflushすることで子プロセスに渡す。
なお、byteで入力しないとダメぽいので、b"heloo"みたいな形になる。
wait_with_outputで、子プロセスの終了を待ちつつ、出力も受け取る。
5. 子プロセスの実行完了を指定時間まで待つ
hr.icon
code: rust
use std::process::Command;
use std::time::Duration;
use wait_timeout::ChildExt;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 長時間実行するプロセスを起動
let mut child = Command::new("sleep")
.arg("5") // 10秒スリープ
.spawn()?;
println!("子プロセスを開始しました (PID: {:?})", child.id());
println!("3秒のタイムアウトを待っています...");
match child.wait_timeout(Duration::new(3, 0)) {
Ok(Some(status)) => {
println!("子プロセスは正常に終了しました (ステータス: {:?})", status);
}
Ok(None) => match child.kill() {
Ok(_) => println!("プロセスを強制終了しました"),
Err(e) => println!("プロセスの強制終了に失敗しました: {}", e),
},
Err(e) => {
println!("エラーが発生しました: {}", e);
}
}
Ok(())
}
ポイント
wait-timeoutというクレートによって、プロセス操作にタイムアウトを加えれる。
trait ChildExtをuseで持ってきておかないと使えないので注意(Rustの仕様)
3秒待っても終わってないならkillする。
6. コマンド出力のリアルタイム処理
hr.icon
code: rust
use std::process::{Command, Stdio};
use std::io::{BufReader, BufRead};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 出力を生成するコマンドを実行(例えば、1秒ごとに数字を出力)
let mut child = Command::new("bash")
.args("-c", "for i in {1..5}; do echo \"Line $i\"; sleep 1; done")
.stdout(Stdio::piped())
.spawn()?;
// 子プロセスの出力をリアルタイムで読み取る
if let Some(stdout) = child.stdout.take() {
let reader = BufReader::new(stdout);
for line in reader.lines() {
match line {
Ok(line) => println!("リアルタイム出力: {}", line),
Err(e) => println!("読み取り中にエラーが発生しました: {}", e),
}
}
}
// 子プロセスの終了を待つ
let status = child.wait()?;
println!("子プロセスが終了しました: {}", status);
Ok(())
}
ポイント
プロセスと関係ないけど、take()はOption型から中身を抜き出す関数。
抜き出した中身があるなら、BufReaderにstdoutを渡すことで、そこから読み取れる。
reader.line()はiteratorを返し、子プロセスが終わらない限り無限に出力すると思われる。